/*
 * Copyright (c) Huawei Technologies Co., Ltd. 2022-2022. All rights reserved.
 */

package com.huawei.saashousekeeper.config;

import com.huawei.saashousekeeper.config.adapter.DefaultSchemaAdapter;
import com.huawei.saashousekeeper.config.adapter.SchemaAdapter;
import com.huawei.saashousekeeper.config.balancestrategy.LoadBalanceStrategy;
import com.huawei.saashousekeeper.config.balancestrategy.RandomStrategy;
import com.huawei.saashousekeeper.config.dynamicdatasource.DataSourceRegistry;
import com.huawei.saashousekeeper.config.dynamicdatasource.DynamicRoutingDataSource;
import com.huawei.saashousekeeper.creator.DataSourceCreator;
import com.huawei.saashousekeeper.creator.DruidDataSourceCreator;
import com.huawei.saashousekeeper.dbpool.druid.DruidDynamicDataSourceConfiguration;
import com.huawei.saashousekeeper.interceptor.FeignRequestInterceptor;
import com.huawei.saashousekeeper.interceptor.MybatisInterceptor;
import com.huawei.saashousekeeper.interceptor.MybatisReadWriteSeparationInterceptor;
import com.huawei.saashousekeeper.interceptor.RabbitMqAspect;
import com.huawei.saashousekeeper.interceptor.TenantDomainFilter;
import com.huawei.saashousekeeper.properties.DynamicSourceProperties;
import com.huawei.saashousekeeper.properties.TenantProperties;

import lombok.extern.log4j.Log4j2;

import org.springframework.amqp.AmqpException;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessagePostProcessor;
import org.springframework.amqp.rabbit.config.DirectRabbitListenerContainerFactory;
import org.springframework.amqp.rabbit.config.SimpleRabbitListenerContainerFactory;
import org.springframework.amqp.rabbit.connection.CachingConnectionFactory;
import org.springframework.amqp.rabbit.connection.ConnectionFactory;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
import org.springframework.boot.autoconfigure.amqp.DirectRabbitListenerContainerFactoryConfigurer;
import org.springframework.boot.autoconfigure.amqp.SimpleRabbitListenerContainerFactoryConfigurer;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;

import java.util.List;
import java.util.Optional;

import javax.sql.DataSource;

/**
 * 自动装配
 *
 * @since 2022-02-14
 */
@Configuration
@EnableConfigurationProperties(
    value = {TenantProperties.class, DynamicSourceProperties.class})
@Log4j2
@AutoConfigureBefore(value = DataSourceAutoConfiguration.class,
    name = "com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceAutoConfigure")
@Import(value = {DruidDynamicDataSourceConfiguration.class})
@ConditionalOnProperty(prefix = "spring.datasource.dynamic", name = "enable", havingValue = "true")
public class TenantAutoConfiguration {

    /**
     * mybatis schema选择拦截器
     *
     * @return mybatis拦截器
     */
    @Bean
    public MybatisInterceptor mybatisInterceptor() {
        return new MybatisInterceptor();
    }

    /**
     * mybatis读写分离拦截器
     *
     * @return mybatis拦截器
     */
    @Bean
    public MybatisReadWriteSeparationInterceptor mybatisReadWriteSeparationInterceptor() {
        return new MybatisReadWriteSeparationInterceptor();
    }

    /**
     * feign调用传递租户标识
     *
     * @return 租户标识传递
     */
    @Bean
    public FeignRequestInterceptor feignRequestInterceptor() {
        return new FeignRequestInterceptor();
    }

    /**
     * MQ消息接收时保存租户标识
     *
     * @return AOP切面
     */
    @Bean
    public RabbitMqAspect rabbitMqAspect() {
        return new RabbitMqAspect();
    }

    /**
     * MQ消息发送时拦截
     *
     * @param factory CachingConnectionFactory
     * @return RabbitTemplate
     */
    @Bean("tenantRouteRabbitTemplate")
    @ConditionalOnProperty(prefix = "spring.schema", name = "rabbitmq-route-enabled", havingValue = "true")
    public RabbitTemplate rabbitTemplate(CachingConnectionFactory factory) {
        RabbitTemplate rabbitTemplate = new RabbitTemplate(factory);

        // 消息发送前保存租户标识
        rabbitTemplate.setBeforePublishPostProcessors(new MessagePostProcessor() {
            @Override
            public Message postProcessMessage(Message message) throws AmqpException {
                String tenantDomain = TenantContext.getDomain();
                log.info("发送消息前的tenantDomain : " + tenantDomain);
                // 消息发送前保存租户标识
                Optional.ofNullable(tenantDomain)
                    .ifPresent(domain -> message.getMessageProperties().setHeader("tenantDomain", tenantDomain));
                return message;
            }
        });
        return rabbitTemplate;
    }

    /**
     * 消息接收后拦截
     *
     * @param configurer 配置
     * @param connectionFactory 连接工厂
     * @return SimpleRabbitListenerContainerFactory
     */
    @Bean("rabbitListenerContainerFactory")

    // 默认的监听模式为simple
    @ConditionalOnProperty(prefix = "spring.rabbitmq.listener", name = "type", havingValue = "simple",
        matchIfMissing = true)
    @ConditionalOnBean(name = "tenantRouteRabbitTemplate")
    public SimpleRabbitListenerContainerFactory simpleRabbitListenerContainerFactory(
        SimpleRabbitListenerContainerFactoryConfigurer configurer, ConnectionFactory connectionFactory) {
        SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory();
        factory.setAfterReceivePostProcessors(message -> {

            // 消息不为空就去获取租户标识
            Optional.ofNullable(message).ifPresent(msg -> {
                String tenantDomain = msg.getMessageProperties().getHeader("tenantDomain");

                // TODO 如果为空则抛出路由异常
                Optional.ofNullable(tenantDomain).orElseThrow(RuntimeException::new);
                TenantContext.setDomain(tenantDomain);
            });
            return message;
        });
        configurer.configure(factory, connectionFactory);
        return factory;
    }

    /**
     * 直连场景
     * 如果是direct模式则装配这个bean
     * @param configurer 配置
     * @param connectionFactory 连接工厂
     * @return DirectRabbitListenerContainerFactory
     */
    @Bean("rabbitListenerContainerFactory")
    @ConditionalOnProperty(prefix = "spring.rabbitmq.listener", name = "type", havingValue = "direct")
    @ConditionalOnBean(name = "tenantRouteRabbitTemplate")
    public DirectRabbitListenerContainerFactory directRabbitListenerContainerFactory(
        DirectRabbitListenerContainerFactoryConfigurer configurer, ConnectionFactory connectionFactory) {
        DirectRabbitListenerContainerFactory factory = new DirectRabbitListenerContainerFactory();
        factory.setAfterReceivePostProcessors(message -> {
            // 消息不为空就去获取租户标识
            Optional.ofNullable(message).ifPresent(msg -> {
                String tenantDomain = msg.getMessageProperties().getHeader("tenantDomain");

                // TODO 如果为空则抛出路由异常
                Optional.ofNullable(tenantDomain).orElseThrow(RuntimeException::new);
                TenantContext.setDomain(tenantDomain);
            });
            return message;
        });
        configurer.configure(factory, connectionFactory);
        return factory;
    }

    /**
     * 租户标识过滤器
     *
     * @return TenantDomainFilter
     */
    @Bean
    public TenantDomainFilter tenantDomainFilter() {
        return new TenantDomainFilter();
    }

    @Bean(value = "routingFilter")
    public FilterRegistrationBean filterRegistrationBean() {
        FilterRegistrationBean<TenantDomainFilter> registrationBean = new FilterRegistrationBean<>();
        registrationBean.setFilter(tenantDomainFilter());
        return registrationBean;
    }

    /**
     * 支持多数据源，多schema配置
     *
     * @param property 配置属性
     * @param loadBalanceStrategies 负载均衡策略列表
     * @param dataSourceCreators 数据源创建器
     * @return 动态数据源
     */
    @Bean
    public DataSource dynamicDataSource(DynamicSourceProperties property,
        List<LoadBalanceStrategy> loadBalanceStrategies, List<DataSourceCreator> dataSourceCreators) {
        // 传入全局配置
        DataSourceRegistry registry = new DynamicRoutingDataSource(loadBalanceStrategies, dataSourceCreators, property);
        return (DataSource) registry;
    }

    /**
     * 负载均衡-随机算法
     *
     * @return 随机算法
     */
    @Bean
    public LoadBalanceStrategy randomStrategy() {
        return new RandomStrategy();
    }

    /**
     * 数据源创建器-druid
     *
     * @return druid数据源创建器
     */
    @Bean
    public DataSourceCreator druidDataSourceCreator() {
        return new DruidDataSourceCreator();
    }

    /**
     * 默认的schema转换逻辑，需要进行schema路由时使用
     *
     * @return 默认schema适配器
     */
    @Bean
    @ConditionalOnMissingBean(SchemaAdapter.class)
    public SchemaAdapter defaultSchemaAdapter() {
        return new DefaultSchemaAdapter();
    }
}
